package es.ucm.fdi.xtremeaudio.modelo.dao;

import java.io.File;
import java.io.PrintStream;
import java.util.Map;

import javazoom.jlgui.basicplayer.BasicController;
import javazoom.jlgui.basicplayer.BasicPlayer;
import javazoom.jlgui.basicplayer.BasicPlayerEvent;
import javazoom.jlgui.basicplayer.BasicPlayerException;
import javazoom.jlgui.basicplayer.BasicPlayerListener;
import es.ucm.fdi.xtremeaudio.controlador.Controlador;
import es.ucm.fdi.xtremeaudio.modelo.beans.Bean;
import es.ucm.fdi.xtremeaudio.modelo.beans.Biblioteca;
import es.ucm.fdi.xtremeaudio.modelo.fachada.Fachada;

/**
 * <ul>
 * <li><b>Proyecto:</b> XtremeAudio</li>
 * <li><b>Grupo:</b> 7 <i>(4&deg;B)</i></li>
 * <li><b>Asignatura:</b> Ingenier&iacute;a del Software</li>
 * <li><b>Curso:</b> 2010/2011</li>
 * <li><b>Centro:</b> Universidad Complutense de Madrid</li>
 * </ul>
 * 
 * El <b>Player</b> implementa un BasicPlayerListener para manejar eventos de
 * reproducci&oacute;n con un reproductor simple basado en BasicPlayer.
 * <p>
 * BasicPlayer funciona sobre JavaSound SPIs para soportar m&uacute;ltiples
 * formatos de audio. B&aacute;sicamente, JavaSound soporta WAV, AU y AIFF. Las
 * librer&iacute;as MP3 SPI y Vorbis SPI ayudan a dar soporte para MP3 y Ogg
 * Vorbis.<br/>
 * <i>V&eacute;ase</i> <a
 * href="http://www.javazoom.net/jlgui/developerguide.html">JavaZoom</a>.
 * </p>
 * Soporta las funcionalidades b&aacute;sicas de reproducci&oacute;n (<i>carga,
 * play, pausa, stop, rebobinado, etc.</i>) y mantiene par&aacute;metros
 * relativos al estado de la reproducci&o;n (<i>volumen, mute, etc.</i>).
 * <br/>
 * <b>Archivos soportados testeados: </b> mp3, ogg, wav<b>*</b> y flac<b>**</b>.<br/>
 * <b>*</b> no soporta tags.<br/>
 * <b>**</b> soporte parcial.
 * 
 * @author &Aacute;lvaro G&oacute;mez Porris
 * @author Elena Hern&aacute;ndez Delgado
 * @author Mar&iacute;a Trinidad Mart&iacute;n Campos
 * @author Mois&eacute;s Mart&iacute;nez Montes
 * @author Mat&iacute;as Salinero Delgado
 * 
 * @version %I%, %G%
 * 
 * @since 1.0
 * 
 * @see BasicPlayer
 * @see BasicPlayerListener
 * @see Bean
 * @see Biblioteca
 * @see Controlador
 * @see Fachada
 */
public class Player implements BasicPlayerListener {

	private boolean aleatorio = false;
	private int bytesRead = 0;
	private BasicPlayer control = new BasicPlayer();
	private boolean mute = false;
	private EstadoRepetir repetir = EstadoRepetir.R_OFF;
	private PrintStream salida = System.out;
	private double volumen = 0.5;

	/**
	 * Contructora por defecto.
	 * 
	 * Establece la salida est&aacute;ndar para imprimir, el control del
	 * reproductor y la ganancia de volumen y balance a un nivel intermedio,
	 * adem&aacute;s de los valores por defecto de reproducci&oacute;n para el
	 * modo aleatorio, mute y repetir (<i>OFF</i>).
	 */
	public Player() {
		control.addBasicPlayerListener(this);
		try {
			control.setPan(0.0);
		} catch (BasicPlayerException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Accesora para conocer el n&uacute;mero de bytes le&iacute;dos
	 * 
	 * @return Los bytes.
	 */
	public int getBytesRead() {
		return bytesRead;
	}

	/**
	 * Accesora de {@link #control}.
	 * 
	 * @return El controlador del {@link BasicPlayer}
	 */
	public BasicPlayer getControl() {
		return control;
	}

	/**
	 * Accesora de {@link #repetir}. Obtiene el estado actual del modo repetir.
	 * 
	 * @return El estado actual.
	 */
	public EstadoRepetir getRepetir() {
		return repetir;
	}

	/**
	 * Accesora para la salida establecida.
	 * 
	 * @return {@link PrintStream} de salida.
	 */
	public PrintStream getSalida() {
		return salida;
	}

	/**
	 * Devuelve el estado actual del control de la reproducci&oacute;n.
	 * 
	 * @return El estado actual.
	 */
	public int getStatus() {
		return control.getStatus();
	}

	/**
	 * Accesora de {@link #volumen}.
	 * 
	 * @return El volumen.
	 */
	public double getVolumen() {
		return volumen;
	}

	/**
	 * Accesora de {@link #aleatorio}.
	 * 
	 * @return La aleatoriedad o no en la reproducci&oacute;n.
	 */
	public boolean isAleatorio() {
		return aleatorio;
	}

	/**
	 * Accesora de {@link #mute}
	 * 
	 * @return Estado del mute.
	 */
	public boolean isMute() {
		return mute;
	}

	/**
	 * Permite conmutar el sonido entre <i>ON</i> y <i>OFF</i>. Al pasar de
	 * nuevo a modo <i>ON</i> tras haber hecho mute, se retorna al valor que
	 * hab&iacute;a antes de hacerlo, gracias al {@link #volumen}.
	 */
	public void mute() {
		if (mute) {
			try {
				control.setGain(volumen);
			} catch (BasicPlayerException e) {
				e.printStackTrace();
			}
		} else {
			try {
				control.setGain(0.0);
			} catch (BasicPlayerException e) {
				e.printStackTrace();
			}
		}
		mute = !mute;
	}

	/**
	 * Carga un archivo en el reproductor.
	 * 
	 * @param sArchivo
	 *            Path del archivo.
	 */
	public void open(String sArchivo) {
		try {
			control.open(new File(sArchivo));
		} catch (BasicPlayerException e) {
			e.printStackTrace();
		}
	}

	/**
	 * <u>M&eacute;todo sobreescrito</u>.
	 * 
	 * Imprime las propiedades del archivo abierto. El mapa de propiedades
	 * incluye, entre otros, bitrate, duraci&oacute;n, frecuencia, canales,
	 * n&uacute;mero de frames, etc.
	 * 
	 * @param stream
	 *            (File, URL o InputStream).
	 * @param props
	 *            Mapa de propiedades.
	 */
	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void opened(Object stream, Map props) {
		display("Abierto : " + props.toString());
	}

	/**
	 * Pausa la reproducci&oacute;n.
	 */
	public void pause() {
		try {
			control.pause();
		} catch (BasicPlayerException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Comienza a reproducir, desde el principio, la canci&oacute;n previamente
	 * cargada.
	 */
	public void play() {
		try {
			control.play();
			control.setGain(volumen);
			if (mute) {
				control.setGain(0.0);
			}
		} catch (BasicPlayerException e) {
			e.printStackTrace();
		}

	}

	/**
	 * Este m&eacute;todo es llamado por el propio {@link BasicPlayer} varias
	 * veces en un segundo mientras est&aacute; en estado de
	 * reproducci&oacute;n.
	 * 
	 * El mapa de propiedades incluye, entre otros, bitrate, duraci&oacute;n,
	 * frecuencia, canales, frames, etc.
	 * 
	 * @param bytesRead
	 *            Bytes le&iacute;dos en el archivo actual.
	 * @param microsegs
	 *            Microsegundos transcurridos (<i><b>OJO:</b> reseteado tras
	 *            hacer un seek</i>).
	 * @param pcmData
	 *            Datos PCM.
	 * @param props
	 *            Mapa de propiedades. 
	 */

	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void progress(int bytesRead, long microsegs, byte[] pcmData,
			Map props) {
		display("Progreso : " + props.toString());
		setBytesRead(bytesRead);
	}

	/**
	 * Contin&uacute;a la reproducci&oacute;n pausada desde el punto en que se
	 * detuvo por &uacute;ltima vez.
	 */
	public void resume() {
		try {
			control.resume();
		} catch (BasicPlayerException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Establece el estado de la reproducci&oacute;n en el byte indicado por
	 * bytes.
	 * 
	 * @param bytes
	 *            El numero de byte respecto al origen.
	 */
	public void seek(long bytes) {
		try {
			control.seek(bytes);
			if (!mute) {
				control.setGain(volumen);
			} else {
				control.setGain(0.0);
			}
		} catch (BasicPlayerException e) {
			e.printStackTrace();
		}

	}

	/**
	 * Modificadora de {@link #aleatorio}.
	 * 
	 * @param aleatorio
	 *            El nuevo modo de aleatoriedad.
	 */
	public void setAleatorio(boolean aleatorio) {
		this.aleatorio = aleatorio;
	}

	/**
	 * Modificadora de {@link #bytesRead}.
	 * 
	 * @param bytes
	 *            El nuevo n&uacute;mero de bytes le&iacute;dos.
	 */
	public void setBytesRead(int bytes) {
		bytesRead = bytes;
	}

	/**
	 * Modificadora de {@link #control}.
	 * 
	 * @param control
	 *            El nuevo controlador de la reproducci&oacute;n.
	 */
	public void setControl(BasicPlayer control) {
		this.control = control;
	}

	/**
	 * <u>M&eacute;todo sobreescrito</u> por implementar una interfaz. Irrelevante.
	 */
	@Override
	public void setController(BasicController controller) {
	}

	/**
	 * Establece la ganancia de volumen en un rango entre 0.0 y 1.0,
	 * actualizando el valor de {@link #volumen}.
	 * 
	 * @param vol
	 *            La nueva ganancia.
	 */
	public void setGanancia(double vol) {
		try {
			volumen = vol;
			if (!mute) {
				control.setGain(volumen);
			}
		} catch (BasicPlayerException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Modificadora de {@link #mute}.
	 * 
	 * @param mute
	 *            El nuevo valor de mute.
	 */
	public void setMute(boolean mute) {
		this.mute = mute;
	}

	/**
	 * Modificadora de {@link #repetir}.
	 * 
	 * @param repetir
	 *            El nuevo estado del modo de repetici&oacute;n.
	 */
	public void setRepetir(EstadoRepetir repetir) {
		this.repetir = repetir;
	}

	/**
	 * Modificadora de {@link #salida}.
	 * 
	 * @param salida
	 *            La nueva salida.
	 */
	public void setSalida(PrintStream salida) {
		this.salida = salida;
	}

	/**
	 * Modificadora de {@link #volumen}.
	 * 
	 * @param volumen
	 *            El nuevo nivel de volumen.
	 */
	public void setVolumen(double volumen) {
		this.volumen = volumen;
	}

	/**
	 * <u>M&eacute;todo sobreescrito</u>. Muestra por la {@link #salida} las
	 * notificaciones de eventos del reproductor (play, pausa, etc.).
	 * 
	 * @param event
	 *            El evento correspondiente.
	 */
	@Override
	public void stateUpdated(BasicPlayerEvent event) {
		display("Estado actualizado : " + event.toString());
	}

	/**
	 * Detiene la reproducci&o;n y resetea al inicio.
	 */
	public void stop() {
		try {
			seek(0);
			control.stop();
		} catch (BasicPlayerException e) {
			e.printStackTrace();
		}
	}

	/*--- M�TODOS PRIVADOS ---*/
	private void display(String msg) {
		if (salida != null) {
			salida.println(msg);
		}
	}

}